#include <chrono>
#include <memory>
#include "AbstractSocket.h"
#ifdef WIN32
#include <winsock2.h>
#include <winsock.h>
#include <Ws2tcpip.h>
#define INIT_SOCKET(res) \
    WORD version = MAKEWORD(2, 2); \
    WSADATA wsadata; \
    int error = 0; \
    error = WSAStartup(version, &wsadata); \
    if (0 != error) \
    { \
        res = false; \
        return; \
    } \
    if (LOBYTE(wsadata.wVersion) != 2 || HIBYTE(wsadata.wHighVersion) != 2) \
    { \
        res = false; \
        return; \
    }
#define CLEAN_SOCKET() WSACleanup();
#define CHECK_SOCKET(res, socket) \
    if(INVALID_SOCKET == socket) \
    { \
        res = false; \
    }
#define CLOSE(socket) closesocket(socket)
#else
#define INIT_SOCKET(res) res=true;
#define CLEAN_SOCKET()
#define CHECK_SOCKET(res, socket) \
    if(-1 == socket) \
    { \
        res = false; \
        close(socket); \
    }
#define CLOSE(socket) close(socket)
#define SOCKET_ERROR -1

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <cerrno>
#include <malloc.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <cstdarg>
#include <fcntl.h>
#include <fcntl.h>
#endif

using namespace sh;

namespace shType
{
typedef std::chrono::high_resolution_clock clock;

typedef std::chrono::microseconds microseconds;
}

AbstractSocket::AbstractSocket()
    : initResult(true), isConnected(false), port(0)
{
    INIT_SOCKET(initResult)
    clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    CHECK_SOCKET(initResult, clientSocket)

}

AbstractSocket::~AbstractSocket()
{
    if (isConnected)
    {
        CLOSE(clientSocket);
    }
    CLEAN_SOCKET()
}

void AbstractSocket::connectToHost(const std::string &hostName, int port)
{
    if (!initResult)
    {
        return;
    }
    this->hostName = hostName;
    this->port = port;

    sockaddr_in sock_addr;
    sock_addr.sin_family = AF_INET;
    sock_addr.sin_addr.s_addr = inet_addr(hostName.c_str());
    sock_addr.sin_port = htons(port);

    int res = connect(clientSocket, (sockaddr *) &sock_addr, sizeof(sockaddr));
    isConnected = (SOCKET_ERROR != res);
}

void AbstractSocket::disconnectFromHost()
{
    if (!initResult)
    {
        return;
    }
    if (isConnected)
    {
        CLOSE(clientSocket);
    }
}

bool AbstractSocket::waitForConnected(int msecs /*= 30000*/)
{
    if (!initResult)
    {
        return false;
    }
    if (isConnected)
    {
        return true;
    }
    std::chrono::time_point<shType::clock> begin = shType::clock::now();
    while (std::chrono::duration_cast<shType::microseconds>(shType::clock::now() - begin).count() <= msecs)
    {
        connectToHost(hostName, port);
        if (isConnected)
        {
            return true;
        }
    }
    return false;
}
int AbstractSocket::writeData(const char *data, int size)
{
    if (!initResult || !isConnected)
    {
        return -1;
    }
    return send(clientSocket, data, size, 0);
}
int AbstractSocket::writeData(const std::string &data)
{
    if (!initResult || !isConnected)
    {
        return -1;
    }
    return send(clientSocket, data.data(), data.length(), 0);
}
int AbstractSocket::readData(char *data, int maxSize)
{
    if (!initResult || !isConnected)
    {
        return -1;
    }
    return recv(clientSocket, data, maxSize, 0);
}
std::string AbstractSocket::readData(int maxSize)
{
    if (!initResult || !isConnected)
    {
        return {};
    }
    std::unique_ptr<char[]> buf(new(std::nothrow)
    char[maxSize + 1]);
    int size = recv(clientSocket, buf.get(), maxSize, 0);
    if (size <= 0)
    {
        return {};
    }
    return std::string(buf.get(), size);
}

